/*
 * sdmmc_spi.c
 *
 *  Created on: Oct 16, 2013
 *      Author: Ken Arok
 */

#include "spi.h"
#include "crc.h"
#include "sdmmc_spi.h"

#ifdef DEBUG
#include <stdio.h>
#include "print_debug.h"
#include "config_debug.h"

#if DEBUG_SDMMC_SPI
static char _buf_dbg[100];
#define SDMMC_SPI_DEBUGF(...)	do {	\
	snprintf(_buf_dbg, 100, __VA_ARGS__); \
	print_dbg((const char*)_buf_dbg);	\
}while(0);
#else
#define SDMMC_SPI_DEBUGF(...)
#endif /* DEBUG_SPI_STACK */
#endif /* DEBUG */


/* SD/MMC SPI characteristic options. */
static spi_channel_options_t SDMMC_SPI_OPTIONS = {
	.baudrate = 400000,		//!> 400 kHz clock frequency.
	.bits = 8,				//!> 8-bit data length.
	.delay_bs = 4,			//!> 4 x 30ns = 120ns TCES.
	.delay_bct = 0,			//!> 0 x 1us = 0us
	.keep_actv = 1,			//!> Drive CS logic manualy.
	.spi_mode = SPI_MODE_0,	//!> Mode 0 of SPI.
	.master_setup = true	//!> Setup in master mode.
};

static spi_descriptor_t spi_sdmmc_desc;

/* Current position (byte) of the transfer. */
static uint32_t sdmmc_spi_transfert_pos;
/* Size block. */
static uint16_t sdmmc_spi_block_size = 512;
/* Total number of block. */
static uint16_t sdmmc_spi_nb_block;

/** \brief Write byte of data to SD/MMC.
 *
 * \param _pbuf		Pointer to buffer where data will be copied.
 * \param _len		Number byte will be copied.
 *
 */
static void sdmmc_spi_write_byte(const void *_pbuf, uint32_t _len);

/** \brief Read byte of data from SD/MMC.
 *
 * \param _pbuf		Pointer to buffer where data will be copied.
 * \param _len		Number byte will be copied.
 *
 */
static void sdmmc_spi_read_byte(void *_pbuf, uint32_t _len);

/** \brief Sends the correct TOKEN on the line to start a read block transfer
 *
 * \return 0 if success, otherwise error.
 */
static uint8_t sdmmc_spi_start_read_block(void);

/** \brief Executed the end of a read block transfer
 */
static void sdmmc_spi_stop_read_block(void);

/** \brief Sends the correct TOKEN on the line to start a write block transfer
 */
static void sdmmc_spi_start_write_block(void);

/** \brief Waits the TOKEN which notify the end of write block transfer
 *
 * \return 0 if success, otherwise error.
 */
static uint8_t sdmmc_spi_stop_write_block(void);

/** \brief Executed the end of a multi blocks write transfer
 *
 * \return 0 if success, otherwise error.
 */
static uint8_t sdmmc_spi_stop_multiwrite_block(void);

/** \brief Wait the end of busy on DAT0 line
 *
 * \return true if success, otherwise false
 */
static bool sdmmc_spi_wait_busy(void);

/* ------------------------------------------------------------------------------------------------- */
static void sdmmc_spi_write_byte(const void *_pbuf, uint32_t _len)
{
	spi_write_byte(spi_sdmmc_desc, _pbuf, _len);
}


static void sdmmc_spi_read_byte(void *_pbuf, uint32_t _len)
{
	spi_read_byte(spi_sdmmc_desc, _pbuf, _len);
}


static uint8_t sdmmc_spi_start_read_block(void)
{
	uint32_t i;
	uint8_t _token;

	/* Wait for start data token:
	 * The read timeout is the Nac timing.
	 * Nac must be computed trough CSD values,
	 * or it is 100ms for SDHC / SDXC
	 * Compute the maximum timeout:
	 * Frequency maximum = 25MHz
	 * 1 byte = 8 cycles
	 * 100ms = 312500 x sd_mmc_spi_drv_read_packet() maximum
	 */
	_token = 0;
	i = 500000;
	do {
		i--;
		if (i == 0) {
			return 3;
		}
		sdmmc_spi_read_byte((void *)&_token, 1);
		if (SPI_TOKEN_DATA_ERROR_VALID(_token)) {
			if (_token & (SPI_TOKEN_DATA_ERROR_ERROR | SPI_TOKEN_DATA_ERROR_ECC_ERROR | SPI_TOKEN_DATA_ERROR_CC_ERROR)) {
				return 1;
			}
			else {
				return 2;
			}
		}
	} while(_token != SPI_TOKEN_SINGLE_MULTI_READ);

	return 0;
}


static void sdmmc_spi_stop_read_block(void)
{
	uint8_t _crc[2];
	/* Read 16-bit CRC (not checked). */
	sdmmc_spi_read_byte((void *)_crc, 2);
}


static void sdmmc_spi_start_write_block(void)
{
	const uint8_t _dummy = 0xFF;
	uint8_t _token;

	/* Delay before start write block:
	   Nwr timing minimum = 8 cycles. */
	sdmmc_spi_write_byte((const void *)&_dummy, 1);

	/* Send start token. */
	if(sdmmc_spi_nb_block > 1) {
		_token = SPI_TOKEN_MULTI_WRITE;
	}
	else {
		_token = SPI_TOKEN_SINGLE_WRITE;
	}
	sdmmc_spi_write_byte((const void *)&_token, 1);
}


static uint8_t sdmmc_spi_stop_write_block(void)
{
	uint8_t _token, _retval = 0;
	uint16_t _crc;

	/* Send CRC. CRC is disabled in SPI mode. */
	_crc = 0xFFFF;
	sdmmc_spi_write_byte((const void *)&_crc, 2);
	/* Receive data response token. */
	sdmmc_spi_read_byte((void *)&_token, 1);
	if (!SPI_TOKEN_DATA_RESP_VALID(_token)) {
		return 1;
	}

	/* Check data response. */
	switch (SPI_TOKEN_DATA_RESP_CODE(_token)) {
		case SPI_TOKEN_DATA_RESP_ACCEPTED:
		break;

		case SPI_TOKEN_DATA_RESP_CRC_ERR:
			_retval = 2;
		return false;
		case SPI_TOKEN_DATA_RESP_WRITE_ERR:
		default:
			_retval = 3;
		return false;
	}

	return _retval;
}


static uint8_t sdmmc_spi_stop_multiwrite_block(void)
{
	const uint8_t _dummy = 0xFF;
	const uint8_t _token = SPI_TOKEN_STOP_TRAN;

	if (1 == sdmmc_spi_nb_block) {
		/* Single block write. */
		return 0; //
	}
	if (sdmmc_spi_nb_block > (sdmmc_spi_transfert_pos / sdmmc_spi_block_size)) {
		/* It is not the End of multi write */
		return 0;
	}

	/* Delay before start write block:
	   Nwr timing minimum = 8 cycles. */
	sdmmc_spi_write_byte((const void *)&_dummy, 1);

	/* Send stop token. */
	sdmmc_spi_write_byte((void *) &_token, 1);

	/* Wait busy. */
	if (sdmmc_spi_wait_busy() == false) {
		return 1;
	}
	return 0;
}


static bool sdmmc_spi_wait_busy(void)
{
	uint8_t _line = 0xFF;
	uint32_t _nec_timeout = 200000;

	/* Delay before check busy
	 * Nbr timing minimum = 8 cylces
	 */
	sdmmc_spi_read_byte((void *) &_line, 1);

	/* Wait end of busy signal
	 * Nec timing: 0 to unlimited
	 * However a timeout is used.
	 * 200,000 * 8 cycles
	 */

	sdmmc_spi_read_byte((void *)&_line, 1);
	do {
		sdmmc_spi_read_byte((void *)&_line, 1);
		if (!(_nec_timeout--)) {
			return false;
		}
	}while (_line != 0xFF);

	return true;
}

/* ----------------------------------------------------------------------------------- */
bool sdmmc_spi_select_device(void)
{
	if(SPI_STATUS_OK == spi_select_chip(spi_sdmmc_desc)) {
		return true;
	}

	return false;
}


bool sdmmc_spi_deselect_device(void)
{
	if(SPI_STATUS_OK == spi_deselect_chip(spi_sdmmc_desc)) {
		return true;
	}

	return false;
}


void sdmmc_spi_init(void)
{
	const char spi_sdmmc_port[] = "SPISDMMC";

	/* Open SPI Port. */
	spi_sdmmc_desc = spi_open(spi_sdmmc_port, 0);
	if(spi_sdmmc_desc) {
		/* Configure SPI parameter for SD/MMC. */
		spi_setup(spi_sdmmc_desc, &SDMMC_SPI_OPTIONS);
	}
}


void sdmmc_spi_setup_device(uint32_t _clock)
{
	SDMMC_SPI_OPTIONS.baudrate = _clock;
	spi_setup(spi_sdmmc_desc, &SDMMC_SPI_OPTIONS);
}


void sdmmc_spi_send_clock(void)
{
	const uint8_t _dummy[10] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

	/* Send 80 cycles. */
	spi_write_byte(spi_sdmmc_desc, (const void *)_dummy, 10);
}


uint8_t sdmmc_spi_send_cmd(uint8_t _cmd, uint32_t _arg)
{
	const uint8_t _dummy = 0xFF;
	uint8_t _r1;
	uint8_t _ncr_timeout;
	uint8_t _command[6];

	_command[0] = SPI_CMD_ENCODE(_cmd);
	_command[1] = (_arg >> 24);
	_command[2] = (_arg >> 16);
	_command[3] = (_arg >> 8);
	_command[4] = _arg;
	_command[5] = vCompute_CRC7(_command, 5);

	/* Select device. */
	while(true != sdmmc_spi_select_device());

	/* 8 cycles to respect Ncs timing
	   Note: This byte does not include start bit "0",
	   thus it is ignored by card. */
	sdmmc_spi_write_byte(&_dummy, 1);

	/* Write Command */
	sdmmc_spi_write_byte((const void *)_command, sizeof(_command));

	/* Wait for response */
	_r1 = 0xFF;
	/* Ignore first byte because NCR min. = 8 clock cycles. */
	sdmmc_spi_read_byte((void *)&_r1, 1);

	_ncr_timeout = 20;
	do {
		/* 8 cycles */
		sdmmc_spi_read_byte((void *)&_r1, 1);
		_ncr_timeout--;
	}while((_r1 & R1_SPI_ERROR) && _ncr_timeout);

	/* De-select device. */
	sdmmc_spi_deselect_device();

	return _r1;
}


uint32_t sdmmc_spi_get_response(void)
{
	uint8_t _tmp[4];

	sdmmc_spi_select_device();
	sdmmc_spi_read_byte((void *)_tmp, 4);
	sdmmc_spi_deselect_device();

	return *(uint32_t *)_tmp;
}


uint8_t sdmmc_spi_get_cid_csd_register(uint8_t *_pbuf)
{
	uint32_t _retry;
	uint8_t _token;

	sdmmc_spi_select_device();
	_retry = 500000;
	do {
		if (_retry-- == 0) {
			sdmmc_spi_deselect_device();
			return 3;
		}
		sdmmc_spi_read_byte((void *)&_token, 1);
		if (SPI_TOKEN_DATA_ERROR_VALID(_token)) {
			sdmmc_spi_deselect_device();
			if (_token & (SPI_TOKEN_DATA_ERROR_ERROR | SPI_TOKEN_DATA_ERROR_ECC_ERROR | SPI_TOKEN_DATA_ERROR_CC_ERROR)) {
				return 1;
			}
			else {
				return 2;
			}
		}
	}while(_token != SPI_TOKEN_SINGLE_MULTI_READ);

	sdmmc_spi_read_byte((void *)_pbuf, 16);
	sdmmc_spi_deselect_device();
	return 0;
}


uint8_t sdmmc_spi_get_scr_register(uint8_t *_pbuf)
{
	uint32_t _retry;
	uint8_t _token;

	sdmmc_spi_select_device();
	_retry = 500000;
	do {
		if (_retry-- == 0) {
			sdmmc_spi_deselect_device();
			return 3;
		}
		sdmmc_spi_read_byte((void *)&_token, 1);
		if (SPI_TOKEN_DATA_ERROR_VALID(_token)) {
			sdmmc_spi_deselect_device();
			if (_token & (SPI_TOKEN_DATA_ERROR_ERROR | SPI_TOKEN_DATA_ERROR_ECC_ERROR | SPI_TOKEN_DATA_ERROR_CC_ERROR)) {
				return 1;
			}
			else {
				return 2;
			}
		}
	} while(_token != SPI_TOKEN_SINGLE_MULTI_READ);

	sdmmc_spi_read_byte((void *)_pbuf, 8);
	sdmmc_spi_deselect_device();
	return 0;
}


uint8_t sdmmc_spi_read_dword(uint32_t *_value)
{
	/* Select device. */
	sdmmc_spi_select_device();

	if (!(sdmmc_spi_transfert_pos % sdmmc_spi_block_size)) {
		/* New block. */
		if (sdmmc_spi_start_read_block()) {
			/* De-select device. */
			sdmmc_spi_deselect_device();
			return 1;
		}
	}
	/* Read data. */
	sdmmc_spi_read_byte((void *)_value, 4);
	*_value = le32_to_cpu(*_value);
	sdmmc_spi_transfert_pos += 4;

	if (!(sdmmc_spi_transfert_pos % sdmmc_spi_block_size)) {
		/* End of block. */
		sdmmc_spi_stop_read_block();
	}

	/* De-select device. */
	sdmmc_spi_deselect_device();
	return 0;
}


uint8_t sdmmc_spi_write_dword(uint32_t _value)
{
	uint8_t _retval;

	/* Select device. */
	sdmmc_spi_select_device();

	if (!(sdmmc_spi_transfert_pos % sdmmc_spi_block_size)) {
		/* New block. */
		sdmmc_spi_start_write_block();
	}

	/* Write data */
	_value = cpu_to_le32(_value);
	sdmmc_spi_write_byte((const void *)&_value, 4);
	sdmmc_spi_transfert_pos += 4;

	if (!(sdmmc_spi_transfert_pos % sdmmc_spi_block_size)) {
		/* End of block. */
		_retval = sdmmc_spi_stop_write_block();
		if (_retval) {
			/* De-select device. */
			sdmmc_spi_deselect_device();
			return _retval;
		}
		/* Wait busy due to data programmability. */
		if (sdmmc_spi_wait_busy() == false) {
			/* De-select device. */
			sdmmc_spi_deselect_device();
			return 3;
		}
	}

	_retval = sdmmc_spi_stop_multiwrite_block();

	/* De-select device. */
	sdmmc_spi_deselect_device();
	return _retval;
}


uint8_t sdmmc_spi_read_block(void *_dest, uint16_t _nb_block)
{
	uint32_t _pos = 0;
	uint8_t *_ptr = _dest, _retval = 0;

	/* Select device. */
	sdmmc_spi_select_device();

	while(_nb_block--) {

		_retval = sdmmc_spi_start_read_block();
		if (_retval) {
			/* De-select device. */
			sdmmc_spi_deselect_device();
			return _retval;
		}

		/* Read block. */
		sdmmc_spi_read_byte((void *)(_ptr + _pos), sdmmc_spi_block_size);
		_pos += sdmmc_spi_block_size;
		sdmmc_spi_transfert_pos += sdmmc_spi_block_size;

		sdmmc_spi_stop_read_block();
	}

	/* De-select device. */
	sdmmc_spi_deselect_device();

	return _retval;
}


uint8_t sdmmc_spi_write_block(const void *_src, uint16_t _nb_block)
{
	uint32_t _pos = 0;;
	const uint8_t *_ptr = _src;

	/* Select device. */
	sdmmc_spi_select_device();

	while (_nb_block--) {

		sdmmc_spi_start_write_block();

		/* Write block. */
		sdmmc_spi_write_byte((const void *)(_ptr + _pos), sdmmc_spi_block_size);
		_pos += sdmmc_spi_block_size;
		sdmmc_spi_transfert_pos += sdmmc_spi_block_size;

		if (sdmmc_spi_stop_write_block()) {
			/* De-select device. */
			sdmmc_spi_deselect_device();
			return 1;
		}
		/* Do not check busy of last block. */
		if (_nb_block) {
			/* Wait busy due to data programmability. */
			if (sdmmc_spi_wait_busy() == false) {
				/* De-select device. */
				sdmmc_spi_deselect_device();
				return 3;
			}
		}
	}

	/* De-select device. */
	sdmmc_spi_deselect_device();
	return 0;
}


uint8_t sdmmc_spi_wait_end_of_write_block(void)
{
	uint8_t _retval = 0;

	/* Select device. */
	sdmmc_spi_select_device();

	/* Wait busy due to data programmability of last block written. */
	if (sdmmc_spi_wait_busy() == false) {
		/* De-select device. */
		sdmmc_spi_deselect_device();
		return 3;
	}
	_retval = sdmmc_spi_stop_multiwrite_block();

	/* De-select device. */
	sdmmc_spi_deselect_device();
	return _retval;
}

